// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{collections::{hash_map::Entry, HashMap}, ops::Range};

use fixedbitset::FixedBitSet;
use petgraph::{dot::{Config, Dot}, graph::{EdgeReference, Edges, NodeIndex}, visit::{EdgeRef, Visitable}, Direction, Graph};

use crate::database::local::pkg_stat_path;

use super::DepKind;

#[derive(Default)]
pub struct DepGraph {
    pub(super) graph: Graph<String, DepKind>,
    pub(super) node_map: HashMap<String, NodeIndex>,
    pub specs_range: Range<usize>,
}

#[derive(Debug, thiserror::Error)]
pub enum DepNodeParseError {
    #[error("bad format")]
    BadFormat,
    #[error("bad sha256")]
    BadSha256,
    #[error("bad arch")]
    BadArch,
}

impl DepGraph {
    pub fn dot(&self) -> Dot<'_, &Graph<String, DepKind>> {
        fn get_node_attributes(_graph: &Graph<String, DepKind>, node_ref: (NodeIndex, &String)) -> String {
            let (id, spec_str) = node_ref;
            format!("label=\"{}({})\"", spec_str, id.index())
        }

        fn get_edge_attributes(_graph: &Graph<String, DepKind>, edge_ref: EdgeReference<'_, DepKind>) -> String {
            let edge = edge_ref.weight();
            format!("label=\"{}\"", edge)
        }

        Dot::with_attr_getters(
            &self.graph,
            &[Config::NodeNoLabel, Config::EdgeNoLabel],
            &get_edge_attributes,
            &get_node_attributes,
        )
    }
    
    pub fn node_count(&self) -> usize {
        self.graph.node_count()
    }

    pub fn visit_map(&self) -> FixedBitSet {
        self.graph.visit_map()
    }

    pub fn node(&self, id: NodeIndex) -> Option<&String> {
        self.graph.node_weight(id)
    }

    pub fn node_id(&self, spec_str: &str) -> Option<NodeIndex> {
        self.node_map.get(spec_str).copied()
    }

    pub fn dependant_edges(&self, id: NodeIndex) -> Edges<'_, DepKind, petgraph::Directed> {
        self.graph.edges_directed(id, Direction::Outgoing)
    }

    pub(super) fn add_node(&mut self, spec_string: String) -> NodeIndex {
        match self.node_map.entry(spec_string.clone()) {
            Entry::Occupied(entry) => *entry.get(),
            Entry::Vacant(entry) => {
                let id = self.graph.add_node(spec_string);
                entry.insert(id);
                id
            }
        }
    }

    pub fn collect_runtime_dependants(&self, start_ids: Vec<NodeIndex>) -> FixedBitSet {
        let mut dependants = self.visit_map();
        let mut unresolved = start_ids;
        while let Some(id) = unresolved.pop() {
            if dependants.contains(id.index()) {
                continue;
            }

            debug_assert!(pkg_stat_path(self.node(id).unwrap()).exists());
            dependants.set(id.index(), true);
            for dependant_edge in self.dependant_edges(id) {
                let dependant_node_id = dependant_edge.target();
                match dependant_edge.weight() {
                    DepKind::Build | DepKind::HostBuild => {},
                    DepKind::Runtime => unresolved.push(dependant_node_id),
                }
            }
        }
        dependants
    }
}
